#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

void ShortHelpAndExit(int _status);
void PrintFullHelp();

bool dump(unsigned char* addr);
bool cpconvert(unsigned char* _codepagetable,FILE* _fSource,FILE* _fDestination);
static unsigned char CharSet[256];
static unsigned int  action = 0;

class TCSZArrayWrapper
{
    public:
        TCSZArrayWrapper(const char** _ppcszArray, size_t _sizeArray, unsigned int _Bitmask, const char* _pcszHelp);
        ~TCSZArrayWrapper() {};
        inline unsigned const int Bitmask() const { return m_Bitmask; }
        inline const size_t Size() const { return m_Size; }
        inline const char* const operator [] (unsigned int _Index) const
            { return m_ppcszArray ? ((_Index < Size()) ? m_ppcszArray[_Index] : NULL) : NULL; }
        inline const char* const Help() const { return m_pcszHelp; }
    private:
        TCSZArrayWrapper();
        TCSZArrayWrapper& operator = (const TCSZArrayWrapper);
        const size_t        m_Size;
        const  char**       m_ppcszArray;
        const  char*        m_pcszHelp;
        unsigned const int  m_Bitmask;
};

TCSZArrayWrapper::TCSZArrayWrapper(
    const char** _ppcszArray,
    size_t _sizeArray,
    unsigned int _Bitmask,
    const char* _pcszHelp
    ):
    m_Size( _ppcszArray ? _sizeArray : 0 ),
    m_Bitmask(_Bitmask),
    m_ppcszArray(_ppcszArray),
    m_pcszHelp(_pcszHelp)
{
}

#define ARRAYSIZE(ArrayOfObjects) (sizeof(ArrayOfObjects)/sizeof(ArrayOfObjects[1]))
#define WRAP_OPTIONS(szArray, Bitmask, Help) TCSZArrayWrapper(szArray, ARRAYSIZE(szArray), Bitmask, Help)

#define     SHOWHELP    1
#define     CONVERT     2
#define     SHOWCHARSET 4

#define     INCOMPATIBLE_OPTIONS (SHOWCHARSET|CONVERT)

static const char* OPTIONS_HELP[]	=	{ "help",   "?",    "h" };
static const char* OPTIONS_CONVERT[]=   { "convert","ct",   "c" };
static const char* OPTIONS_SHOW[]   =   { "show",   "sh",   "s" };

static const char* hlpO_HELP=   "Shows help";
static const char* hlpO_CP  =   "Starts conversion";
static const char* hlpO_CS  =   "Shows charset file";

TCSZArrayWrapper OPTIONS[] ={ WRAP_OPTIONS(OPTIONS_HELP,     SHOWHELP,   hlpO_HELP)
                            , WRAP_OPTIONS(OPTIONS_CONVERT,  CONVERT,    hlpO_CP)
                            , WRAP_OPTIONS(OPTIONS_SHOW,     SHOWCHARSET,hlpO_CS)
                            };


int main(int argc, char *argv[])
{
    printf("cpcnvrt CODEPAGE UTILITY, v.1.0. Perlin V.A. 2000 (c)\n");
    if ( argc < 2 )
    {
        printf("Too few parameters\n");
        ShortHelpAndExit(4);
    }

    int             nOptions = 0;
    for (int nArgCounter = 1 ; nArgCounter < argc ; nArgCounter++)
    {
        char*   pszOption = argv[nArgCounter];
        if ( (strlen(pszOption) > 1) && (pszOption[0] == '-') )
        {
            ++pszOption;
            int nNumberOfOptionSets = ARRAYSIZE(OPTIONS);
            int nFoundOptions = nOptions;
            for (int nOptionSet = 0 ; nOptionSet < nNumberOfOptionSets ; nOptionSet++)
            {
                TCSZArrayWrapper& option = OPTIONS[nOptionSet];
                size_t nSynonimsInOptionSet = option.Size();
                for (unsigned int nSynonim = 0 ; nSynonim < nSynonimsInOptionSet ; nSynonim++)
                {
                    if (!strcmp(option[nSynonim],pszOption))//,strlen(option[nSynonim])))
                    {
                        unsigned const int uBitmask = option.Bitmask();
                        if ( action & uBitmask )
                        {
                            printf("Option %s defined more than once\n",pszOption);
                            ShortHelpAndExit(13);
                        }
                        action |= option.Bitmask();
                        ++nOptions;
                    }
                }
            }
            if (nFoundOptions == nOptions)
            {
                printf("Unknown option %s\n",pszOption);
                ShortHelpAndExit(13);
            }
        }
        else
        {
            break;
        }
    }

    if (!nOptions)
    {
        printf("No options found\n");
        ShortHelpAndExit(1);
    }

    if (action & SHOWHELP)
    {
        PrintFullHelp();
    }
    if ( !(action & (~SHOWHELP)) )
    {
        if ( (argc - nOptions) > 1 )
        {
            for (int nUnkParam = nOptions+1; nUnkParam < argc ; nUnkParam++)
            {
                printf("Unknown parameter %s\n",argv[nUnkParam]);
            }
            ShortHelpAndExit(12);
        }
    }
    else
    {
        printf("\n");
    }
    if ( (action & SHOWCHARSET) || (action & CONVERT) )
    {
        if ( (argc - nOptions) < 2 )
        {
            printf("No charset file path provided\n");
            ShortHelpAndExit(5);
        }
        if ( (argc - nOptions) > 4 )
        {
            printf("Too many input parameters\n");
            ShortHelpAndExit(6);
        }
        if ( !(action & (~(SHOWCHARSET|SHOWHELP))) )
        {
            if ( (argc - nOptions) > 2 )
            {
                for (int nUnkParam = nOptions+2; nUnkParam < argc ; nUnkParam++)
                {
                    printf("Unknown parameter %s\n",argv[nUnkParam]);
                }
                ShortHelpAndExit(12);
            }
        }
        const char* pcszCharsetFile = argv[nOptions+1];
        FILE*   fCharSet = fopen(pcszCharsetFile, "rb");
        if (fCharSet == NULL)
        {
            printf("Can't open charset file %s\n",pcszCharsetFile);
            exit(7);
        }
        size_t sizeToRead = ARRAYSIZE(CharSet);
        size_t sizeActual = fread(CharSet, sizeof(CharSet[0]),sizeToRead , fCharSet);
        fclose(fCharSet);
        //fCharSet = NULL;
        if (sizeActual != sizeToRead)
        {
            printf("Conversion file should be at least %d bytes long.\n\
Only %d bytes were read from %s\n", sizeToRead, sizeActual,pcszCharsetFile);
            exit(8);
        }

        if (action & SHOWCHARSET)
        {
            dump(&CharSet[0]);
            if (action & CONVERT)
                printf("\n");
        }
        if (action & CONVERT)
        {
            const char* pcszSourceFile = NULL;
            const char* pcszDestinationFile = NULL;
            if ( (argc - nOptions) < 3 )
            {
                printf("No source file path provided\n");
                ShortHelpAndExit(9);
            }
            else
            {
                pcszSourceFile = argv[nOptions+2];
            }
            FILE*   fSource = fopen(pcszSourceFile, "rb");
            if (fSource == NULL)
            {
                printf("Can't open source file %s\n",pcszSourceFile);
                exit(10);
            }

            FILE*   fDestination;
            if ( (argc - nOptions) < 4 )
            {
                printf("No destination file path provided. Using stdout.\n");
                fDestination = stdout;

            }
            else
            {
                pcszDestinationFile = argv[nOptions+3];
                fDestination = fopen(pcszDestinationFile, "w+b");
                if (fDestination == NULL)
                {
                    printf("Can't create destination file %s",pcszDestinationFile);
                    exit(11);
                }
            }
            cpconvert(&CharSet[0], fSource, fDestination);
            fclose(fDestination);
            fclose(fSource);
        }
    }
    return 0;
}


void PrintUsage()
{
    printf("Usage:\ncpcnvrt option[s] [charset] [source] [destination]\n\n");
}

void ShortHelpAndExit(int _status)
{
    if ( !(action & SHOWHELP) )
    {
        PrintUsage();
        printf("For more info run: cpcnvrt -%s\n",OPTIONS_HELP[0]);
    }
    exit(_status);
}

void PrintFullHelp()
{
    printf("\nConverts source binary file to destination binary file using\n\
charset file as an ASCII character conversion table. Charset\n\
file is assumed to be 256 bytes long, extra bytes are ignored.\n\n");
    PrintUsage();
    printf("[option]\tcase-sensitive option preceeded by - character\n");
    printf("\t\toptions should be delimited by blanks\n");
    printf("[charset]\tcharset convertion table file\n");
    printf("[source]\tinput file path\n");
    printf("[destination]\toutput file path or stdout if omitted\n");
    printf("\nSupported options:\n");
    for (int nOptionSet = 0 ; nOptionSet < ARRAYSIZE(OPTIONS) ; nOptionSet++)
    {
        printf("\n");
        TCSZArrayWrapper& option = OPTIONS[nOptionSet];
        for (unsigned int nSynonim = 0 ; nSynonim < option.Size() ; nSynonim++)
        {
            printf("\t%s",option[nSynonim]);
        }
        printf("\t- %s",option.Help());
    }
    printf("\n");
}

bool dump(unsigned char* addr)
{
    bool result = (addr) ? true : false;
    if (result)
    {
        unsigned char i,j;

    //    times=((long)addr) % 16;
    //    times=16-times;
        printf("   ");
        for ( i= 0 ; i < 16 ; i++ )
        {
            printf ("--%1X",i);
        }
        printf("--");
        for ( i= 0 ; i < 16 ; i++ )
        {
            printf ("%1X",i);
        }
        printf("\n");

        for (i=0 ; i < 16 ; i++)
        {
            printf ("%2X: ",i);//addr);
            for (j=0 ; j < 16 ; j++)
            {
                unsigned char cSymbol = *(addr+j);
                printf("%02x ",cSymbol);
            }
            printf(" ");
            for (j=0 ; j < 16 ; j++)
            {
                if (*addr > 0x1F)
                {
                    printf("%c",*addr++);
                }
                else
                {
                    printf (".");
                    addr++;
                }
            }
            printf("\n");
        }
    }
    return result;
}


bool cpconvert(unsigned char* _codepagetable,FILE* _fSource,FILE* _fDestination)
{
    bool result = (_codepagetable && _fSource && _fDestination) ? true : false;
    if (result)
    {
        printf("CONVERTING...\n");
        static unsigned char cr;
        static unsigned char cw;
        while ( fread(&cr, sizeof(cr), 1, _fSource) == 1)
        {
            cw = _codepagetable[cr];
            if (cw != 0x1A || _fDestination != stdout)
            {
                if ( fwrite(&cw, sizeof(cw), 1, _fDestination) != 1)
                {
                   printf("Can't write to destination file, aborting\n");
                   result = false;
                   break;
                }
            }
        }
    }
    return result;
}

